import os
import json
import numpy as np
import pandas as pd
from pathlib import Path
from datetime import datetime
from typing import Dict, List, Optional, Any, Union, Tuple


class SystemFeatureVector:
    """
    系统特征向量类
    
    用于存储和管理以下内容:
    1. 系统级别偏差(SLD)特征张量 - shape: [win_size, 1, channel_dim]
    2. 实例级别偏差(ILD)特征张量 - shape: [win_size, instance_num, channel_dim]
    3. 相似度结果 - DataFrame格式，包含每个实例与SLD的相似度、排名等信息
    """
    
    def __init__(
        self, 
        sld_tensor: Optional[np.ndarray] = None,
        ild_tensor: Optional[np.ndarray] = None,
        timestamps: Optional[List[float]] = None,
        idx_to_cmdb_id: Optional[Dict[int, str]] = None,
        similarity_results: Optional[pd.DataFrame] = None,
        anomaly_id: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None,
        output_dir: Optional[str] = None,
        auto_save: bool = False
    ):
        """
        初始化系统特征向量
        
        参数:
            sld_tensor: 系统级别偏差特征张量 [win_size, 1, channel_dim]
            ild_tensor: 实例级别偏差特征张量 [win_size, instance_num, channel_dim]
            timestamps: 时间戳列表
            idx_to_cmdb_id: 索引到CMDB ID的映射
            similarity_results: 相似度计算结果DataFrame
            anomaly_id: 异常ID
            metadata: 特征相关元数据
            output_dir: 输出目录路径
            auto_save: 是否自动保存特征和结果
        """
        self.sld_tensor = sld_tensor
        self.ild_tensor = ild_tensor
        self.timestamps = timestamps or []
        self.idx_to_cmdb_id = idx_to_cmdb_id or {}
        self.similarity_results = similarity_results
        self.anomaly_id = anomaly_id
        self.metadata = metadata or {}
        
        # 在构造函数中计算相似度
        if self.similarity_results is None and self.sld_tensor is not None and self.ild_tensor is not None:
            self.compute_similarity_results()
        
        # 添加基本元数据
        self._update_metadata()
        
        # 如果设置了自动保存，则保存特征和结果
        if auto_save and output_dir:
            self.save(output_dir)
        
    def _update_metadata(self):
        """更新元数据信息"""
        self.metadata["created_at"] = datetime.now().isoformat()
        
        if self.sld_tensor is not None:
            self.metadata["sld_tensor_shape"] = list(self.sld_tensor.shape)
            
        if self.ild_tensor is not None:
            self.metadata["ild_tensor_shape"] = list(self.ild_tensor.shape)
            
        if self.similarity_results is not None:
            self.metadata["similarity_stats"] = {
                "min": float(self.similarity_results["similarity"].min()),
                "max": float(self.similarity_results["similarity"].max()),
                "mean": float(self.similarity_results["similarity"].mean()),
                "median": float(self.similarity_results["similarity"].median())
            }
            
    def compute_similarity_results(self) -> pd.DataFrame:
        """
        计算ILD与SLD之间的相似度，并生成结果DataFrame
        
        返回:
            pd.DataFrame: 包含相似度、排名等信息的DataFrame
        """
        if self.ild_tensor is None or self.sld_tensor is None:
            return pd.DataFrame()
            
        results = []
        for t in range(len(self.timestamps)):
            sld = self.sld_tensor[t, 0]  # [channel_dim]
            ild_t = self.ild_tensor[t]   # [instance_num, channel_dim]
            
            # 计算余弦相似度
            norm_sld = np.linalg.norm(sld)
            norm_ild = np.linalg.norm(ild_t, axis=1)
            similarities = np.zeros(len(ild_t))
            mask = (norm_sld > 0) & (norm_ild > 0)
            similarities[mask] = np.dot(ild_t[mask], sld) / (norm_ild[mask] * norm_sld)
            
            # 为每个实例创建一条记录
            for i in range(len(ild_t)):
                results.append({
                    "timestamp": self.timestamps[t],
                    "instance_id": self.idx_to_cmdb_id.get(i, f"instance_{i}"),
                    "similarity": similarities[i],
                    "l1_norm": np.sum(np.abs(ild_t[i]))
                })
                
        # 创建DataFrame并添加排名信息
        df = pd.DataFrame(results)
        df["rank"] = df.groupby("timestamp")["similarity"].rank(
            ascending=False, method="min"
        )
        df["similarity_percentile"] = df.groupby("timestamp")["similarity"].transform(
            lambda x: x.rank(pct=True) * 100
        )
        
        # 按时间戳和相似度排序
        df = df.sort_values(["timestamp", "similarity"], ascending=[True, False])
        
        self.similarity_results = df
        return df
        
    def get_top_similar_instances(self, top_n: int = 5) -> pd.DataFrame:
        """获取相似度最高的前N个实例"""
        if self.similarity_results is None:
            self.compute_similarity_results()
            
        if self.similarity_results is None:
            return pd.DataFrame()
            
        return self.similarity_results.nlargest(top_n, "similarity")
        
    def get_anomaly_components(self, top_k: int = 3) -> List[str]:
        """获取偏差最大的组件"""
        if self.similarity_results is None:
            self.compute_similarity_results()
            
        if self.similarity_results is None:
            return []
            
        # 使用L1范数和相似度的组合来识别异常组件
        df = self.similarity_results.copy()
        df["anomaly_score"] = df["l1_norm"] * df["similarity"]
        top_instances = df.nlargest(top_k, "anomaly_score")
        return list(top_instances["instance_id"])
        
    def save(self, output_dir: str) -> Dict[str, str]:
        """
        保存特征张量和相似度结果
        
        参数:
            output_dir: 输出目录
            
        返回:
            Dict[str, str]: 保存的文件路径字典
        """
        os.makedirs(output_dir, exist_ok=True)
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        
        saved_files = {}
        
        # 保存SLD张量
        if self.sld_tensor is not None:
            sld_path = os.path.join(output_dir, f"sld_tensor.npy")
            np.save(sld_path, self.sld_tensor)
            saved_files["sld_tensor"] = sld_path
            
        # 保存ILD张量
        if self.ild_tensor is not None:
            ild_path = os.path.join(output_dir, f"ild_tensor.npy")
            np.save(ild_path, self.ild_tensor)
            saved_files["ild_tensor"] = ild_path
            
        # 保存相似度结果
        if self.similarity_results is not None:
            sim_path = os.path.join(output_dir, f"similarity_results.csv")
            self.similarity_results.to_csv(sim_path, index=False)
            saved_files["similarity_results"] = sim_path
            
        # 保存元数据
        metadata = self.metadata.copy()
        metadata["saved_at"] = timestamp
        metadata["saved_files"] = saved_files
        metadata["timestamps"] = self.timestamps
        metadata["idx_to_cmdb_id"] = self.idx_to_cmdb_id
        
        meta_path = os.path.join(output_dir, f"metadata.json")
        with open(meta_path, "w") as f:
            json.dump(metadata, f, indent=2)
        saved_files["metadata"] = meta_path
        
        return saved_files
        
    @classmethod
    def load(cls, metadata_file: str) -> "SystemFeatureVector":
        """从元数据文件加载特征向量"""
        with open(metadata_file, "r") as f:
            metadata = json.load(f)
            
        base_dir = os.path.dirname(metadata_file)
        saved_files = metadata.get("saved_files", {})
        
        # 加载张量数据
        sld_tensor = None
        if "sld_tensor" in saved_files:
            sld_path = os.path.join(base_dir, os.path.basename(saved_files["sld_tensor"]))
            sld_tensor = np.load(sld_path)
            
        ild_tensor = None
        if "ild_tensor" in saved_files:
            ild_path = os.path.join(base_dir, os.path.basename(saved_files["ild_tensor"]))
            ild_tensor = np.load(ild_path)
            
        # 加载相似度结果
        similarity_results = None
        if "similarity_results" in saved_files:
            sim_path = os.path.join(base_dir, os.path.basename(saved_files["similarity_results"]))
            similarity_results = pd.read_csv(sim_path)
            
        # 提取其他信息
        timestamps = metadata.get("timestamps", [])
        idx_to_cmdb_id = metadata.get("idx_to_cmdb_id", {})
        anomaly_id = metadata.get("anomaly_id")
        
        return cls(
            sld_tensor=sld_tensor,
            ild_tensor=ild_tensor,
            timestamps=timestamps,
            idx_to_cmdb_id=idx_to_cmdb_id,
            similarity_results=similarity_results,
            anomaly_id=anomaly_id,
            metadata=metadata
        )
            
            
    def export_similarity_report(self, output_path: str) -> str:
        """导出相似度分析报告"""
        if self.similarity_results is None:
            self.compute_similarity_results()
            
        if self.similarity_results is None:
            return ""
            
        os.makedirs(os.path.dirname(os.path.abspath(output_path)), exist_ok=True)
        
        report = []
        report.append("# 相似度分析报告")
        report.append(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
        report.append(f"异常ID: {self.anomaly_id or '未知'}")
        
        # 添加统计信息
        stats = self.metadata.get("similarity_stats", {})
        report.append("\n## 相似度统计")
        report.append(f"- 最小相似度: {stats.get('min', 0):.4f}")
        report.append(f"- 最大相似度: {stats.get('max', 0):.4f}")
        report.append(f"- 平均相似度: {stats.get('mean', 0):.4f}")
        report.append(f"- 中位相似度: {stats.get('median', 0):.4f}")
        
        # 添加top实例信息
        report.append("\n## 相似度最高的实例")
        top_instances = self.get_top_similar_instances(10)
        for _, row in top_instances.iterrows():
            report.append(
                f"- {row['instance_id']}: "
                f"相似度 {row['similarity']:.4f} "
                f"({row['similarity_percentile']:.1f}%), "
                f"L1范数 {row['l1_norm']:.4f}"
            )
            
        with open(output_path, "w") as f:
            f.write("\n".join(report))
            
        return output_path